package com.linkgie.galaxyframework.shell.utils;

import java.util.regex.Pattern;

import org.jline.utils.AttributedString;
import org.jline.utils.AttributedStyle;
import org.springframework.shell.Input;
import org.springframework.shell.jline.PromptProvider;

import com.linkgie.galaxyframework.shell.CommandAbortedException;
import com.linkgie.galaxyframework.shell.CommandTerminal;
import com.linkgie.galaxyframework.shell.InputOption;
import com.linkgie.galaxyframework.shell.InputOptions;
import com.linkgie.galaxyframework.shell.MaskInputProvider;
import com.linkgie.galaxyframework.shell.OutputHandler;
import com.linkgie.galaxyframework.shell.TerminalProvider;
import com.linkgie.galaxyframework.shell.UserAbortedException;
import com.linkgie.galaxyframework.shell.utils.AnsiStyles.Forecolor;
import com.linkgie.galaxyframework.utils.CollectionUtils;
import com.linkgie.galaxyframework.utils.IntCounter;
import com.linkgie.galaxyframework.utils.concurrent.ThreadUtils;
import com.linkgie.galaxyframework.utils.service.ServiceContext;

public class ShellConsole {

	private static final String FALSE_DESCRIPTION = "否";
	private static final String FALSE_DISPLAY = "No";
	private static final String TRUE_DESCRIPTION = "是";
	private static final String TRUE_DISPLAY = "Yes";
	private static final InputOption DEFAULT_TRUE_OPTION = InputOption.ofValue("yes").display(TRUE_DISPLAY).description(TRUE_DESCRIPTION).complete();
	private static final InputOption DEFAULT_FALSE_OPTION = InputOption.ofValue("no").display(FALSE_DISPLAY).description(FALSE_DESCRIPTION).complete();

	/**
	 * 控制交互设置；
	 */
	public static class Settings implements Cloneable {

		/**
		 * 普通样式；
		 */
		public final AttributedStyle NORMAL_STYLE;

		/**
		 * 警告样式；
		 */
		public final AttributedStyle WARNING_STYLE;

		/**
		 * 错误样式；
		 */
		public final AttributedStyle ERROR_STYLE;

		/**
		 * 密码掩码；
		 */
		public final Character PASSWOR_MASK;

		/**
		 * 错误输入重试次数；
		 */
		public final int RETRY_COUNT;

		/**
		 * 请求用户输入时，是否在输出了提示信息之后进行换行至新的一行后，开始提示用户输入；
		 */
		public final boolean NEW_LINE_INPUT;

		public final char NEW_LINE_INPUT_PROMPT_CHAR;

		public Settings(AttributedStyle defaultStyle, AttributedStyle warningStyle, AttributedStyle errorStyle,
				char passwordMask, int retryCount) {
			this(defaultStyle, warningStyle, errorStyle, passwordMask, retryCount, false, '>');
		}

		public Settings(AttributedStyle defaultStyle, AttributedStyle warningStyle, AttributedStyle errorStyle,
				char passwordMask, int retryCount, boolean newLineInput, char newLineInputPromptChar) {
			NORMAL_STYLE = defaultStyle;
			WARNING_STYLE = warningStyle;
			ERROR_STYLE = errorStyle;
			PASSWOR_MASK = passwordMask;
			RETRY_COUNT = retryCount;

			NEW_LINE_INPUT = newLineInput;
			NEW_LINE_INPUT_PROMPT_CHAR = newLineInputPromptChar;
		}

		@Override
		public Settings clone() {
			try {
				return (Settings) super.clone();
			} catch (CloneNotSupportedException e) {
				throw new IllegalStateException(e.getMessage(), e);
			}
		}

		public Settings setNewLineInput(boolean newLineInput) {
			return new Settings(this.NORMAL_STYLE, this.WARNING_STYLE, this.ERROR_STYLE, this.PASSWOR_MASK,
					this.RETRY_COUNT, newLineInput, this.NEW_LINE_INPUT_PROMPT_CHAR);
		}
	}

	public static Settings DEFAULT_SETTINGS = new Settings(
			AttributedStyle.DEFAULT.foreground(AttributedStyle.WHITE | AttributedStyle.BRIGHT),
			AttributedStyle.DEFAULT.foreground(AttributedStyle.MAGENTA | AttributedStyle.BRIGHT),
			AttributedStyle.DEFAULT.foreground(AttributedStyle.RED | AttributedStyle.BRIGHT), (char) 0, 3);

	private static final TerminalProvider TERMINAL_PROVIDER;

	public static final ShellConsole DEFAULT;

	static {
		TERMINAL_PROVIDER = ServiceContext.load(TerminalProvider.class).getFirstService();
		DEFAULT = new ShellConsole(DEFAULT_SETTINGS.clone(), TERMINAL_PROVIDER);
	}

	public static ShellConsole create(Settings settings) {
		return new ShellConsole(settings, TERMINAL_PROVIDER);
	}

	public final Settings SETTINGS;

	private final String NEW_LINE_INPUT_PROMPT;

	private TerminalProvider terminalProvider;

	private ShellConsole(Settings settings, TerminalProvider terminalProvider) {
		this.SETTINGS = settings;
		this.terminalProvider = terminalProvider;

		this.NEW_LINE_INPUT_PROMPT = " :" + settings.NEW_LINE_INPUT_PROMPT_CHAR + " ";
	}

	private OutputHandler getOutputHandler() {
		CommandTerminal terminal = terminalProvider.getTerminal();
		if (terminal == null) {
			return null;
		}
		return terminal.getOutputHandler();
	}

	private CommandTerminal getTerminal() {
		return terminalProvider.getTerminal();
	}

	private boolean hasContext() {
		return terminalProvider != null && terminalProvider.getTerminal() != null;
	}

	private static AttributedStyle style(AnsiStyles.Forecolor style) {
		if (style == null) {
			return AttributedStyle.DEFAULT;
		}
		return AttributedStyle.DEFAULT.foreground(style.CODE);
	}

	/**
	 * 按指定的选项值读取；
	 * 
	 * <p>
	 * 此方法允许输入不在选项中的值；
	 * 
	 * @param values 候选项；
	 * @return
	 */
	public String readByValues(String[] values, String prompt, Object... args) {
		InputOption[] inputOptions = CollectionUtils.cast(values, InputOption.class,
				(v) -> InputOption.ofValue(v).complete());

		String str = readString(false, inputOptions, prompt, args);
		return str;
	}

	/**
	 * 从候选项中选择一项；
	 * 
	 * <p>
	 * 此方法只允许输入选项中的值；
	 * 
	 * @param values
	 * @return
	 */
	public String selectValue(String[] values, String prompt, Object... args) {
		InputOption[] inputOptions = CollectionUtils.cast(values, InputOption.class,
				(v) -> InputOption.ofValue(v).complete());

		String str = readString(true, inputOptions, prompt, args);
		return str;
	}

	/**
	 * 按指定的选项值读取；
	 * 
	 * <p>
	 * 此方法允许输入不在选项中的值；
	 * 
	 * @param values 候选项；
	 * @return
	 */
	public String readByOptions(InputOption[] options, String prompt, Object... args) {
		String str = readString(false, options, prompt, args);
		return str;
	}

	/**
	 * 按指定的选项值读取；
	 * 
	 * <p>
	 * 此方法允许输入不在选项中的值；
	 * 
	 * @param values 候选项；
	 * @return
	 */
	public String readByOptions(InputOptions options, String prompt, Object... args) {
		String str = readString(false, options.toArray(), prompt, args);
		return str;
	}

	/**
	 * 从候选项中选择一项；
	 * 
	 * <p>
	 * 此方法只允许输入选项中的值；
	 * 
	 * @param values
	 * @return
	 */
	public String selectOption(InputOption[] options, String prompt, Object... args) {
		String str = readString(true, options, prompt, args);
		return str;
	}

	/**
	 * 从候选项中选择一项；
	 * 
	 * <p>
	 * 此方法只允许输入选项中的值；
	 * 
	 * @param values
	 * @return
	 */
	public String selectOption(InputOptions options, String prompt, Object... args) {
		String str = readString(true, options.toArray(), prompt, args);
		return str;
	}

	/**
	 * 如果输入 Yes 或 Y ，则返回 true；<br>
	 * 
	 * 如果输入了其它字符，则返回 false；
	 * 
	 * @param promptFormat
	 * @param args
	 * @return
	 */
	public boolean confirmYes(String promptFormat, Object... args) {
		return confirm(new String[] { "yes", "y" }, null, true, true, promptFormat, args);
	}

	/**
	 * 如果输入 No 或 N ，则返回 false；<br>
	 * 
	 * 如果输入了其它字符，则返回 true；
	 * 
	 * @param promptFormat
	 * @param args
	 * @return
	 */
	public boolean confirmNo(String promptFormat, Object... args) {
		return confirm(null, new String[] { "no", "n" }, true, true, promptFormat, args);
	}

	/**
	 * 如果输入 Yes 或 Y ，则返回 true；<br>
	 * 
	 * 如果输入 No 或 N ，则返回 false；<br>
	 * 
	 * 如果输入了其它字符，则为无效输入，提示重新输入；
	 * 
	 * @param promptFormat
	 * @param args
	 * @return
	 */
	public boolean confirmYesNo(String promptFormat, Object... args) {
		return confirm(new String[] { "yes", "y" }, new String[] { "no", "n" }, true, false, promptFormat, args);
	}

	/**
	 * 如果输入 Yes 或 Y ，则返回 true；<br>
	 * 
	 * 如果输入 No 或 N ，则返回 false；<br>
	 * 
	 * 如果输入了其它字符，则为无效输入，提示重新输入；
	 * 
	 * @param promptFormat
	 * @param args
	 * @return
	 */
	public boolean warnAndConfirmYesNo(String promptFormat, Object... args) {
		return confirm(new String[] { "yes", "y" }, new String[] { "no", "n" }, true, false, SETTINGS.WARNING_STYLE,
				promptFormat, args);
	}

	/**
	 * 如果输入值匹配 trueValues 参数 ，则返回 true；<br>
	 * 
	 * 如果输入值匹配 falseValues 参数 ，则返回 false；<br>
	 * 
	 * 如果输入了其它字符，则为无效输入，提示重新输入；
	 * 
	 * @param trueValues
	 * @param falseValues
	 * @param ignoreCase
	 * @param promptFormat
	 * @param args
	 * @return
	 */
	public boolean confirm(String[] trueValues, String[] falseValues, boolean ignoreCase, String promptFormat,
			Object... args) {
		return confirm(trueValues, falseValues, ignoreCase, false, promptFormat, args);
	}

	/**
	 * 如果输入值匹配 trueValues 参数 ，则返回 true；<br>
	 * 
	 * 如果输入值匹配 falseValues 参数 ，则返回 false；<br>
	 * 
	 * 如果输入了其它字符，则为无效输入，提示重新输入；
	 * 
	 * @param trueValues
	 * @param falseValues
	 * @param ignoreCase
	 * @param nullMatchAny 在没有精确匹配的情况下，是否把 trueValues 和 falseValues 参数为 null
	 *                     视为匹配任意输入；<br>
	 *                     设为 false 时，当输入不匹配 trueValues 和 falseValues 参数包括 null
	 *                     的情况时，视为无效输入而触发重试；
	 * @param promptFormat
	 * @param args
	 * @return
	 */
	public boolean confirm(String[] trueValues, String[] falseValues, boolean ignoreCase, boolean nullMatchAny,
			String promptFormat, Object... args) {
		return confirm(trueValues, falseValues, ignoreCase, nullMatchAny, SETTINGS.NORMAL_STYLE, promptFormat, args);
	}

	/**
	 * 如果输入值匹配 trueValues 参数 ，则返回 true；<br>
	 * 
	 * 如果输入值匹配 falseValues 参数 ，则返回 false；<br>
	 * 
	 * 如果输入了其它字符，则为无效输入，提示重新输入；
	 * 
	 * @param trueValues
	 * @param falseValues
	 * @param ignoreCase
	 * @param nullMatchAny 在没有精确匹配的情况下，是否把 trueValues 和 falseValues 参数为 null
	 *                     视为匹配任意输入；<br>
	 *                     设为 false 时，当输入不匹配 trueValues 和 falseValues 参数包括 null
	 *                     的情况时，视为无效输入而触发重试；
	 * @param promptFormat
	 * @param args
	 * @return
	 */
	public boolean confirm(String[] trueValues, String[] falseValues, boolean ignoreCase, boolean nullMatchAny,
			AnsiStyles.Forecolor color, String promptFormat, Object... args) {
		return confirm(trueValues, falseValues, ignoreCase, nullMatchAny, style(color), promptFormat, args);
	}

	/**
	 * 如果输入值匹配 trueValues 参数 ，则返回 true；<br>
	 * 
	 * 如果输入值匹配 falseValues 参数 ，则返回 false；<br>
	 * 
	 * 如果输入了其它字符，则为无效输入，提示重新输入；
	 * 
	 * @param trueValues
	 * @param falseValues
	 * @param ignoreCase
	 * @param nullMatchAny 在没有精确匹配的情况下，是否把 trueValues 和 falseValues 参数为 null
	 *                     视为匹配任意输入；<br>
	 *                     设为 false 时，当输入不匹配 trueValues 和 falseValues 参数包括 null
	 *                     的情况时，视为无效输入而触发重试；
	 * @param promptFormat
	 * @param args
	 * @return
	 */
	private boolean confirm(String[] trueValues, String[] falseValues, boolean ignoreCase, boolean nullMatchAny,
			AttributedStyle style, String promptFormat, Object... args) {
		// return (boolean) readInput(new BooleanInputAcceptor(trueValues, falseValues,
		// ignoreCase, nullMatchAny), style, promptFormat, args);

		InputOption trueOption = null;
		if (trueValues != null && trueValues.length > 0) {
			trueOption = InputOption.ofValue(trueValues[0]).display(TRUE_DISPLAY).description(TRUE_DESCRIPTION).complete();
		}
		InputOption falseOption = null;
		if (falseValues != null && falseValues.length > 0) {
			falseOption = InputOption.ofValue(falseValues[0]).display(FALSE_DISPLAY).description(FALSE_DESCRIPTION).complete();
		}


		if (trueOption == null && falseOption == null) {
			trueOption = DEFAULT_TRUE_OPTION;
			trueValues = new String[] { DEFAULT_TRUE_OPTION.getValue() };

			falseOption = DEFAULT_FALSE_OPTION;
			falseValues = new String[] { DEFAULT_FALSE_OPTION.getValue() };
		}else if(trueOption == null){
			if (DEFAULT_TRUE_OPTION.getValue().equals(falseOption.getValue())) {
				// 避免默认 true 值与 false 值冲突；
				trueOption = InputOption.ofValue("y").display(TRUE_DISPLAY).description(TRUE_DESCRIPTION).complete();
				trueValues = new String[] { trueOption.getValue() };
			}else{
				trueOption = DEFAULT_TRUE_OPTION;
				trueValues = new String[] { DEFAULT_TRUE_OPTION.getValue() };
			}
		}else if(falseOption == null){
			if (DEFAULT_FALSE_OPTION.getValue().equals(trueOption.getValue())) {
				// 避免默认 true 值与 false 值冲突；
				falseOption = InputOption.ofValue("n").display(FALSE_DISPLAY).description(FALSE_DESCRIPTION).complete();
				falseValues = new String[] { falseOption.getValue() };
			}else{
				falseOption = DEFAULT_FALSE_OPTION;
				falseValues = new String[] { DEFAULT_FALSE_OPTION.getValue() };
			}
		}

		return (boolean) readInput(new BooleanInputAcceptor(trueValues, falseValues, ignoreCase, nullMatchAny), SETTINGS.RETRY_COUNT, style, true, new InputOption[]{trueOption, falseOption},
				promptFormat, args);

	}

	public void println() {
		println(AttributedString.EMPTY);
	}

	public void newline() {
		println(AttributedString.EMPTY);
	}

	public void println(String message) {
		OutputHandler outputHandler = getOutputHandler();
		if (outputHandler != null) {
			outputHandler.println(new AttributedString(message, SETTINGS.NORMAL_STYLE));
		}
	}

	public void println(String format, Object... args) {
		OutputHandler outputHandler = getOutputHandler();
		if (outputHandler != null) {
			String message = String.format(format, args);
			outputHandler.println(new AttributedString(message, SETTINGS.NORMAL_STYLE));
		}
	}

	public void println(AnsiStyles.Forecolor style, String format, Object... args) {
		println(new AttributedString(String.format(format, args), style(style)));
	}

	public void printPrimary(String format, Object... args) {
		println(new AttributedString(String.format(format, args), style(Forecolor.GREEN)));
	}

	public void printWarning(String format, Object... args) {
		println(new AttributedString(String.format(format, args), SETTINGS.WARNING_STYLE));
	}

	public void printError(String format, Object... args) {
		println(new AttributedString(String.format(format, args), SETTINGS.ERROR_STYLE));
	}

	private void println(AttributedString message) {
		OutputHandler outputHandler = getOutputHandler();
		if (outputHandler != null) {
			outputHandler.println(message);
		}
	}

	/**
	 * 以警告消息的方式抛出 CommandAbortedException 异常中断当前命令；
	 * 
	 * @param format
	 * @param args
	 * @throws CommandAbortedException
	 */
	public void warning(String format, Object... args) throws CommandAbortedException {
		throw new CommandAbortedException(String.format(format, args), SETTINGS.WARNING_STYLE);
	}

	/**
	 * 以错误消息的方式抛出 CommandAbortedException 异常中断当前命令；
	 * 
	 * @param format
	 * @param args
	 * @throws CommandAbortedException
	 */
	public void error(String format, Object... args) throws CommandAbortedException {
		throw new CommandAbortedException(String.format(format, args), SETTINGS.ERROR_STYLE);
	}

	/**
	 * 以错误消息的方式抛出 CommandAbortedException 异常中断当前命令；
	 * 
	 * @param cause
	 * @param format
	 * @param args
	 * @throws CommandAbortedException
	 */
	public void error(Throwable cause, String format, Object... args) throws CommandAbortedException {
		throw new CommandAbortedException(String.format(format, args), SETTINGS.ERROR_STYLE, cause);
	}

	/**
	 * 以错误消息的方式抛出 UserAbortedException 异常中断当前命令；
	 * 
	 * @param format
	 * @param args
	 * @throws UserAbortedException
	 */
	public void userError(String format, Object... args) throws UserAbortedException {
		throw new UserAbortedException(String.format(format, args), SETTINGS.ERROR_STYLE);
	}

	public AttributedStyle getInputStyle() {
		return SETTINGS.NORMAL_STYLE;
	}

	public String readPassword(String promptFormat, Object... args) {
		return readString(SETTINGS.PASSWOR_MASK, promptFormat, args);
	}

	public String readPassword(InputAcceptor acceptor, String promptFormat, Object... args) {
		return readString(acceptor, SETTINGS.PASSWOR_MASK, promptFormat, args);
	}

	/**
	 * 从控制台终端输入口令，不允许输入空口令；<br>
	 * 
	 * @param prompt
	 * @param args
	 * @return
	 */
	public String readNotEmptyPassword(String prompt, Object... args) {
		return readPassword(new InputAcceptor() {
			@Override
			public Acception accept(String password) {
				if (password.length() == 0) {
					return Acception.rejectInWarning("输入了空口令！");
				}
				return Acception.accept(password);
			}
		}, prompt, args);

	}

	/**
	 * 从控制台终端输入一个新口令；<br>
	 * 
	 * 新口令要求输入两次并确保一致；
	 * 
	 * @param tagOfPassword 口令的标签名称，用于填充提示信息模板：请输入[tagOfPassword]的口令 ；
	 * @return
	 */
	public String readNewPassword(String tagOfPassword) {
		return readPassword(new InputAcceptor() {
			@Override
			public Acception accept(String password) {
				if (password.length() == 0) {
					return Acception.rejectInWarning("输入了空口令！");
				}
				String password2 = readPassword("再次输入%s的口令：", tagOfPassword);
				if (!password.equals(password2)) {
					return Acception.rejectInWarning("两次输入的口令不一致！");
				}

				return Acception.accept(password);
			}
		}, "请输入%s的口令：", tagOfPassword);

	}

	/**
	 * 从控制台终端输入一个私钥；<br>
	 * 
	 * 要求输入两次并确保一致；
	 * 
	 * @param tag
	 * @return
	 */
	public String readPrivateKey() {
		return readPassword(new InputAcceptor() {
			@Override
			public Acception accept(String password) {
				if (password == null || password.length() == 0) {
					return Acception.rejectInWarning("输入的私钥为空！");
				}
				String password2 = readPassword("再次输入私钥：");
				if (!password.equals(password2)) {
					return Acception.rejectInWarning("两次输入的私钥不一致！");
				}

				return Acception.accept(password);
			}
		}, "请输入私钥：");

	}

	/**
	 * 读取以指定模式输入的文本；
	 * 
	 * @param pattern
	 * @param promptFormat
	 * @param args
	 * @return
	 */
	public String readPattern(String pattern, String promptFormat, Object... args) {
		return readPattern(Pattern.compile(pattern), -1, null, false, promptFormat, args);
	}

	/**
	 * 读取以指定模式输入的文本；
	 * 
	 * @param pattern
	 * @param promptFormat
	 * @param args
	 * @return
	 */
	public String readPattern(String pattern, String defaultValue, String promptFormat, Object... args) {
		return readPattern(Pattern.compile(pattern), -1, defaultValue, defaultValue != null, promptFormat, args);
	}

	/**
	 * 读取以指定模式输入的文本；
	 * 
	 * @param pattern
	 * @param promptFormat
	 * @param args
	 * @return
	 */
	public String readPattern(Pattern pattern, String promptFormat, Object... args) {
		return readPattern(pattern, -1, null, false, promptFormat, args);
	}

	/**
	 * 读取以指定模式输入的文本；
	 * 
	 * @param pattern
	 * @param promptFormat
	 * @param args
	 * @return
	 */
	public String readPattern(Pattern pattern, String defaultValue, String promptFormat, Object... args) {
		return readPattern(pattern, -1, defaultValue, defaultValue != null, promptFormat, args);
	}

	/**
	 * @param pattern      有效格式的正则表达式；如果为 null，则不检查格式；
	 * @param maxLen       最大长度；如果小于等于 0 ，则不检查长度；
	 * @param promptFormat
	 * @param args
	 * @return
	 */
	public String readPattern(Pattern pattern, int maxLen, boolean allowEmpty, String promptFormat, Object... args) {
		return readPattern(pattern, maxLen, null, allowEmpty, promptFormat, args);
	}

	/**
	 * @param pattern      有效格式的正则表达式；如果为 null，则不检查格式；
	 * @param maxLen       最大长度；如果小于等于 0 ，则不检查长度；
	 * @param defaultValue 默认值；
	 * @param allowEmpty
	 * @param promptFormat
	 * @param args
	 * @return
	 */
	public String readPattern(Pattern pattern, int maxLen, String defaultValue, boolean allowEmpty, String promptFormat,
			Object... args) {
		return readString(new InputAcceptor() {
			@Override
			public Acception accept(String input) {
				input = input.trim();
				if (input.length() == 0 && allowEmpty) {
					if (defaultValue != null) {
						return Acception.accept(defaultValue);
					}
					return Acception.accept(input);
				}
				if (pattern != null && !pattern.matcher(input).matches()) {
					return Acception.rejectInWarning("输入格式不匹配！");
				}
				if (maxLen > 0 && input.length() > maxLen) {
					return Acception.rejectInWarning("输入长度超出最大范围 %s！", maxLen);
				}
				return Acception.accept(input);
			}
		}, promptFormat, args);
	}


	public String readString(String promptFormat, Object... args) {
		return readString((InputAcceptor) null, promptFormat, args);
	}

	public String readNotEmptyString(String promptFormat, Object... args) {
		return readString(new InputAcceptor() {
			@Override
			public Acception accept(String input) {
				if (input.length() == 0) {
					return Acception.rejectInWarning("未输入任何字符！");
				}
				return Acception.accept(input);
			}
		}, promptFormat, args);
	}

	/**
	 * @param mask
	 * @param prompt
	 * @return
	 */
	public String readString(Character mask, String promptFormat, Object... args) {
		return (String) readInput((InputAcceptor) null, SETTINGS.RETRY_COUNT, mask, promptFormat, args);
	}

	public String readString(InputAcceptor acceptor, String promptFormat, Object... args) {
		return (String) readInput(acceptor, SETTINGS.RETRY_COUNT, null, promptFormat, args);
	}

	public String readString(InputAcceptor acceptor, Character mask, String promptFormat, Object... args) {
		return (String) readInput(acceptor, SETTINGS.RETRY_COUNT, mask, promptFormat, args);
	}

	/**
	 * @param mask
	 * @param prompt
	 * @return
	 */
	public String readString(boolean optionRequired, InputOption[] options, String promptFormat, Object... args) {
		return (String) readInput((InputAcceptor) null, SETTINGS.RETRY_COUNT, optionRequired, options, promptFormat,
				args);
	}

	/**
	 * 读取终端输入；
	 * 
	 * @param acceptor     终端输入内容的接收器，用于检查和转换终端的输入，并返回检查和转换的结果；如果返回无效结果将触发重新输入；
	 * @param promptFormat 提示信息格式 (参考{@link String#format(String, Object...) })；
	 * @param args         提示消息格式的参数；
	 * @return
	 */
	public Object readInput(InputAcceptor acceptor, String promptFormat, Object... args) {
		return readInput(acceptor, SETTINGS.RETRY_COUNT, null, promptFormat, args);
	}

	/**
	 * 读取终端输入；
	 * 
	 * @param acceptor     终端输入内容的接收器，用于检查和转换终端的输入，并返回检查和转换的结果；如果返回无效结果将触发重新输入；
	 * @param promptFormat 提示信息格式 (参考{@link String#format(String, Object...) })；
	 * @param args         提示消息格式的参数；
	 * @return
	 */
	public Object readInput(InputAcceptor acceptor, AnsiStyles.Forecolor color, String promptFormat, Object... args) {
		return readInput(acceptor, SETTINGS.RETRY_COUNT, style(color), null, promptFormat, args);
	}

	/**
	 * 读取终端输入；
	 * 
	 * @param acceptor     终端输入内容的接收器，用于检查和转换终端的输入，并返回检查和转换的结果；如果返回无效结果将触发重新输入；
	 * @param promptFormat 提示信息格式 (参考{@link String#format(String, Object...) })；
	 * @param args         提示消息格式的参数；
	 * @return
	 */
	private Object readInput(InputAcceptor acceptor, AttributedStyle style, String promptFormat, Object... args) {
		return readInput(acceptor, SETTINGS.RETRY_COUNT, style, null, promptFormat, args);
	}

	/**
	 * 读取终端输入；
	 * 
	 * @param acceptor     终端输入内容的接收器，用于检查和转换终端的输入，并返回检查和转换的结果；如果返回无效结果将触发重新输入；
	 * @param mask         输入掩码；
	 * @param promptFormat 提示信息格式 (参考{@link String#format(String, Object...) })；
	 * @param args         提示消息格式的参数；
	 * @return
	 */
	public Object readInput(InputAcceptor acceptor, Character mask, String promptFormat, Object... args) {
		return readInput(acceptor, SETTINGS.RETRY_COUNT, mask, promptFormat, args);
	}

	/**
	 * 读取终端输入；
	 * 
	 * @param acceptor     终端输入内容的接收器，用于检查和转换终端的输入，并返回检查和转换的结果；如果返回无效结果将触发重新输入；
	 * @param retryCount   输入内容未通过校验时，重新输入的次数；当重试次数超出此参数指定的次数之后，询问是否继续或者终止；
	 * @param promptFormat 提示信息格式 (参考{@link String#format(String, Object...) })；
	 * @param args         提示消息格式的参数；
	 * @return
	 */
	public Object readInput(InputAcceptor acceptor, Integer retryCount, String promptFormat, Object... args) {
		return readInput(acceptor, retryCount, null, promptFormat, args);
	}

	/**
	 * 读取终端输入；
	 * 
	 * @param acceptor     终端输入内容的接收器，用于检查和转换终端的输入，并返回检查和转换的结果；如果返回无效结果将触发重新输入；
	 * @param retryCount   输入内容未通过校验时，重新输入的次数；当重试次数超出此参数指定的次数之后，询问是否继续或者终止；
	 * @param mask         输入掩码；
	 * @param promptFormat 提示信息格式 (参考{@link String#format(String, Object...) })；
	 * @param args         提示消息格式的参数；
	 * @return
	 */
	public Object readInput(InputAcceptor acceptor, Integer retryCount, Character mask, String promptFormat,
			Object... args) {
		return readInput(acceptor, retryCount, SETTINGS.NORMAL_STYLE, mask, promptFormat, args);
	}

	/**
	 * 读取终端输入；
	 * 
	 * @param acceptor       终端输入内容的接收器，用于检查和转换终端的输入，并返回检查和转换的结果；如果返回无效结果将触发重新输入；
	 * @param retryCount     输入内容未通过校验时，重新输入的次数；当重试次数超出此参数指定的次数之后，询问是否继续或者终止；
	 * @param promptFormat   提示信息格式 (参考{@link String#format(String, Object...) })；
	 * @param args           提示消息格式的参数；
	 * @param optionRequired 是否必须输入选项值；
	 * @param options        选项值；
	 * @return
	 */
	public Object readInput(InputAcceptor acceptor, Integer retryCount, boolean optionRequired, InputOption[] options,
			String promptFormat,
			Object... args) {
		return readInput(acceptor, retryCount, SETTINGS.NORMAL_STYLE, optionRequired, options, promptFormat, args);
	}

	/**
	 * 读取终端输入；
	 * 
	 * @param acceptor     终端输入内容的接收器，用于检查和转换终端的输入，并返回检查和转换的结果；如果返回无效结果将触发重新输入；
	 * @param retryCount   输入内容未通过校验时，重新输入的次数；当重试次数超出此参数指定的次数之后，询问是否继续或者终止；
	 * @param mask         输入掩码；
	 * @param promptFormat 提示信息格式 (参考{@link String#format(String, Object...) })；
	 * @param args         提示消息格式的参数；
	 * @return
	 */
	public Object readInput(InputAcceptor acceptor, Integer retryCount, AnsiStyles.Forecolor color, Character mask,
			String promptFormat, Object... args) {
		return readInput(acceptor, retryCount, style(color), mask, promptFormat, args);
	}

	/**
	 * 读取终端输入；
	 * 
	 * @param acceptor     终端输入内容的接收器，用于检查和转换终端的输入，并返回检查和转换的结果；如果返回无效结果将触发重新输入；
	 * @param retryCount   输入内容未通过校验时，重新输入的次数；当重试次数超出此参数指定的次数之后，询问是否继续或者终止；
	 * @param mask         输入掩码；
	 * @param promptFormat 提示信息格式 (参考{@link String#format(String, Object...) })；
	 * @param args         提示消息格式的参数；
	 * @return
	 */
	private Object readInput(InputAcceptor acceptor, Integer retryCount, AttributedStyle style, Character mask,
			String promptFormat, Object... args) {
		if (!hasContext()) {
			throw new IllegalStateException("No terminal context has been provided!");
		}
		CommandTerminal terminal = getTerminal();

		IntCounter counter = new IntCounter(0);
		while (true) {
			countInputTimesAndCheck(retryCount, style, terminal, counter);

			// read;
			String str = readInput(terminal, SETTINGS.NORMAL_STYLE, mask, promptFormat, args);

			// validate;
			if (acceptor == null) {
				return str;
			}
			Acception acpt = acceptor.accept(str);
			if (!acpt.isOk()) {
				println(acpt.getReason());
				continue;
			}

			return acpt.getValue();
		}
	}

	private void countInputTimesAndCheck(Integer retryCount, AttributedStyle style, CommandTerminal terminal,
			IntCounter counter) {
		counter.increase();
		if (counter.value() > retryCount.intValue()) {
			String str = readInput(terminal, style, "是否继续输入？ (任意键继续 | N-退出) ：");
			if ("No".equalsIgnoreCase(str) || "N".equalsIgnoreCase(str)) {
				userError("用户终止输入！\r\n");
			}
			counter.set(1);
		}
	}

	private String readInput(CommandTerminal terminal, AttributedStyle style, String promptFormat, Object... args) {
		return readInput(terminal, style, null, promptFormat, args);
	}

	private String readInput(CommandTerminal terminal, AttributedStyle style, Character mask, String promptFormat,
			Object... args) {
		AttributedStyle origStyle = terminal.getHightlightStyle();
		terminal.setHightlightStyle(style);
		try {
			// read;
			PromptProvider promptProvider = createPromptProvider(style, promptFormat, args);
			MaskInputProvider inputProvider = terminal.getInputProvider();
			Input input = inputProvider.readInput(mask, promptProvider);
			return input.rawText();
		} finally {
			terminal.setHightlightStyle(origStyle);
		}
	}

	/**
	 * 读取终端输入；
	 * 
	 * @param acceptor       终端输入内容的接收器，用于检查和转换终端的输入，并返回检查和转换的结果；如果返回无效结果将触发重新输入；
	 * @param retryCount     输入内容未通过校验时，重新输入的次数；当重试次数超出此参数指定的次数之后，询问是否继续或者终止；
	 * @param optionRequired 是否必须输入选项值；
	 * @param options        选项列表；
	 * @param promptFormat   提示信息格式 (参考{@link String#format(String, Object...) })；
	 * @param args           提示消息格式的参数；
	 * @return
	 */
	private Object readInput(InputAcceptor acceptor, Integer retryCount, AttributedStyle style, boolean optionRequired,
			InputOption[] options, String promptFormat,
			Object... args) {
		if (!hasContext()) {
			throw new IllegalStateException("No terminal context has been provided!");
		}
		CommandTerminal terminal = getTerminal();

		IntCounter counter = new IntCounter(0);
		while (true) {
			countInputTimesAndCheck(retryCount, style, terminal, counter);

			// read;
			String str = readInput(terminal, SETTINGS.NORMAL_STYLE, optionRequired, options, promptFormat, args);

			// 强制要求只能输入选项时，对返回值去掉前后的空白字符；
			if (optionRequired && options != null && options.length > 0) {
				if (str != null) {
					str = str.trim();
				}
			}

			// validate;
			if (acceptor == null) {
				return str;
			}
			Acception acpt = acceptor.accept(str);
			if (!acpt.isOk()) {
				println(acpt.getReason());
				continue;
			}

			return acpt.getValue();
		}
	}

	private String readInput(CommandTerminal terminal, AttributedStyle style, boolean optionRequired,
			InputOption[] options, String promptFormat,
			Object... args) {
		AttributedStyle origStyle = terminal.getHightlightStyle();
		terminal.setHightlightStyle(style);
		try {
			// read;
			PromptProvider promptProvider = createPromptProvider(style, promptFormat, args);
			MaskInputProvider inputProvider = terminal.getInputProvider();
			Input input = inputProvider.readInput(promptProvider, optionRequired, options);
			return input.rawText();
		} finally {
			terminal.setHightlightStyle(origStyle);
		}
	}

	private PromptProvider createPromptProvider(AttributedStyle style, String promptFormat, Object... args) {
		PromptProvider promptProvider = new PromptProvider() {
			@Override
			public AttributedString getPrompt() {
				String promptMessage = null;
				if (promptFormat != null && promptFormat.length() != 0) {
					promptMessage = String.format(promptFormat, args);
				}
				if (promptMessage != null && SETTINGS.NEW_LINE_INPUT) {
					promptMessage = promptMessage + "\r\n" + NEW_LINE_INPUT_PROMPT;
				}
				if (promptMessage == null) {
					promptMessage = "" + NEW_LINE_INPUT_PROMPT;
				}
				return new AttributedString(promptMessage, style);
			}
		};
		return promptProvider;
	}

	/**
	 * 读取整数输入；
	 * 
	 * @return
	 */
	public int readInt(String promptFormat, Object... args) {
		return readInt(null, null, promptFormat, args);
	}

	/**
	 * 读整数输入；
	 * 
	 * @param min          接受的最小值（含）；如果为 null 则不检查最小值；
	 * @param max          接受的最大值（含）；如果为 null 则不检查最大值；
	 * @param promptFormat 提示信息格式 (参考{@link String#format(String, Object...) })；
	 * @param args         提示消息格式的参数；
	 * @return
	 */
	public int readInt(Integer min, Integer max, String promptFormat, Object... args) {
		return readInt(min, max, null, promptFormat, args);
	}

	/**
	 * 读整数输入；
	 * 
	 * @param min          接受的最小值（含）；如果为 null 则不检查最小值；
	 * @param max          接受的最大值（含）；如果为 null 则不检查最大值；
	 * @param promptFormat 提示信息格式 (参考{@link String#format(String, Object...) })；
	 * @param args         提示消息格式的参数；
	 * @return
	 */
	public int readInt(Integer min, Integer max, Integer defaultValue, String promptFormat, Object... args) {
		return (int) readInput(new InputAcceptor() {

			@Override
			public Acception accept(String input) {
				String text = input.trim();
				if (text.length() == 0) {
					if (defaultValue != null) {
						return Acception.accept(defaultValue);
					}
					return Acception.rejectInWarning("输入为空！");
				}
				int value = 0;
				try {
					value = Integer.parseInt(text);
				} catch (NumberFormatException e) {
					return Acception.rejectInWarning("输入内容不是数值！");
				}
				if (min != null && value < min.intValue()) {
					return Acception.rejectInWarning("不能小于[%s] ！", min.intValue());
				}
				if (max != null && value > max.intValue()) {
					return Acception.rejectInWarning("不能大于[%s] ！", max.intValue());
				}
				return Acception.accept(value);
			}
		}, 3, null, promptFormat, args);
	}

	/**
	 * 等待满足特定条件；<br>
	 * 
	 * 此方法以 1 秒的间隔重复检查条件，直到达到超时时间 30 秒以后，将询问是否继续；
	 * 
	 * @param condition 等待的条件；
	 * @return
	 * @throws UserAbortedException
	 */
	public boolean waitForCondition(Condition condition) throws UserAbortedException {
		return waitForCondition(condition, 1000, 30 * 1000, true);
	}

	/**
	 * 等待满足特定条件；
	 * 
	 * @param condition     等待的条件；
	 * @param checkInterval 检查条件的间隔周期；以毫秒为单位；
	 * @param timeout       超时时间；以毫秒为单位；
	 * @return 只有满足条件才会返回 true；如果不满足条件，则继续等待，或者抛出终止异常；
	 * @throws UserAbortedException 用户终止；
	 */
	public boolean waitForCondition(Condition condition, long checkInterval, long timeout) throws UserAbortedException {
		return waitForCondition(condition, checkInterval, timeout, true);
	}

	/**
	 * 等待满足特定条件；
	 * 
	 * @param condition      等待的条件；
	 * @param checkInterval  检查条件的间隔周期；以毫秒为单位；
	 * @param timeout        超时时间；以毫秒为单位；
	 * @param errorOnAborted 用户终止时是否抛出控制台异常消息(通过方法{@link #error(String, Object...)})；
	 *                       如果为 false，则不引发异常，只返回 false；
	 * @return 如果条件满足，则返回 true ；如果等待超时用户终止，则返回 false；
	 * @throws UserAbortedException 用户终止；
	 */
	public boolean waitForCondition(Condition condition, long checkInterval, long timeout, boolean errorOnAborted)
			throws UserAbortedException {
		long startTs = System.currentTimeMillis();
		while (!condition.isOK()) {
			if ((System.currentTimeMillis() - startTs) > timeout) {
				if (confirmYesNo("等待超时！ 是否继续等待？ (Yes/Y - 继续等待 | No/N - 终止操作)：")) {
					println("继续等待......");
					startTs = System.currentTimeMillis();
				} else {
					// 不等待；
					if (errorOnAborted) {
						userError("用户终止操作！");
					}
					return false;
				}
			}
			ThreadUtils.sleep(checkInterval);
		}
		return true;
	}

	public interface Condition {

		boolean isOK();

	}
}
